package main

import (
	"encoding/json"
	"fmt"
	"github.com/hyperledger/fabric/core/chaincode/shim"
	pb "github.com/hyperledger/fabric/protos/peer"
	"strconv"
	"errors"
	"strings"
	"math/big"
	"bytes"
	"time"
)

type WalletTokenChaincode struct {

}

var log *shim.ChaincodeLogger

type Token struct {
	Name   string 	`json:"name"`					// 合约名称
	Symbol string	`json:"symbol"`					// 符号，简称
	Supply *big.Int	`json:"supply"`					// 发行总量
	Decimals int    `json:"decimals"`				// 代币精度
	Owner  string 	`json:"owner"`					// 所有人
	Lock   bool 	`json:"lock"`					// 锁定标识
	LockAccount map[string]bool `json:"lockAccount"`// 锁定用户
}

type Tokens struct {
	Name   string 	`json:"name"`					// 合约名称
	Symbol string   `json:"symbol"`					// 符号，简称
	Supply string 	`json:"supply"`					// 发行总量
	Owner  string 	`json:"owner"`					// 所有人
	Status int 		`json:"status"`
}

var TokenKey = "currencyTokenWnzxMaiyiFang"

func main() {
	err := shim.Start(new(WalletTokenChaincode))
	if err != nil {
		fmt.Errorf(err.Error())
	}
}

func (t WalletTokenChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {

	// 初始化账本基本数据
	_, args := stub.GetFunctionAndParameters()

	if len(args) != 1 {
		return shim.Error(parameterErr.Error())
	}

	// 获取基本数据
	tb, err := stub.GetState(TokenKey)
	if err != nil {
		return shim.Error(err.Error())
	}
	// 如果找到了数据，说明是更新操作，不做处理
	if tb != nil {
		return shim.Success(nil)
	}

	// 否则初始化数据
	symbol := strings.ToLower(args[0])

	var tokens Tokens
	// 从tokenList里面获取数据
	_, err = queryTokenIsExist(stub, symbol)
	if err == nil {
		return shim.Error("指定代币信息不存在，请确认代币信息已经初始化完成")
	} else {
		// 从错误信息中提取地址信息
		tokensStr := err.Error()
		err := json.Unmarshal([]byte(tokensStr), &tokens)
		if err != nil {
			return shim.Error("init error: queryTokenIsExist response error")
		}
	}

	suply, bs := new(big.Int).SetString(tokens.Supply, 10)
	if !bs {
		return shim.Error(parameterErr.Error())
	}
	token := &Token{
		Name:        tokens.Name,
		Symbol:      symbol,
		Supply:      suply,
		Decimals:    8,
		Owner:       tokens.Owner,
		Lock:        false,
		LockAccount: make(map[string]bool),
	}

	tb, err = json.Marshal(token)
	if err != nil {
		return shim.Error(err.Error())
	}
	// 数据存储
	err = stub.PutState(TokenKey, tb)
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

func (t WalletTokenChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {

	log = getLogger(stub)

	fnc, args := stub.GetFunctionAndParameters()

	log.Info(fnc)
	log.Info(args)

	// 该合约只负责数据的业务操作，不负责任何事件，以及流水记录

	if fnc == "" {
		return shim.Error("function is nil")
	} else if fnc == "lockToken" {						// 锁定合约					success
		return t.LockToken(stub, args)
	} else if fnc == "unLockToken" {					// 解锁合约					success
		return t.UnLockToken(stub, args)
	} else if fnc == "lockAccount" {					// 锁定账户					success
		return t.LockAccount(stub, args)
	} else if fnc == "unLockAccount" {					// 解锁账户					success
		return t.UnlockAccount(stub, args)
	} else if fnc == "mint" {							// 增发代币					success
		return t.Mint(stub, args)
	} else if fnc == "burn" {							// 回收代币					success
		return t.Burn(stub, args)
	} else if fnc == "setOwner" {						// 设置所有人					success
		return t.SetOwner(stub, args)
	} else if fnc == "getAccountState" {				// 获取账户在该合约中的状态	success
		return t.GetAccountState(stub, args)
	} else if fnc == "getTokenState" {					// 获取合约状态				success
		return t.GetTokenState(stub, args)
	} else if fnc == "getToken" {						// 获取合约属性				success
		return t.GetToken(stub)
	} else if fnc == "getHis" {
		return t.GetHis(stub)
	}

	return shim.Success(nil)
}

func (t WalletTokenChaincode) GetToken(stub shim.ChaincodeStubInterface) pb.Response {
	token, err := stub.GetState(TokenKey)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(token)
}

func (t WalletTokenChaincode) GetHis(stub shim.ChaincodeStubInterface) pb.Response {
	reslist, err := stub.GetHistoryForKey(TokenKey)
	if err != nil {
		return shim.Error(err.Error())
	}
	res, err := getHisResult(reslist)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(res.Bytes())
}

func (t WalletTokenChaincode) GetAccountState(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 1 {
		return shim.Error(parameterErr.Error())
	}
	token, err := getToken(stub)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success([]byte(strconv.FormatBool(token.LockAccount[args[0]])))
}

func (t WalletTokenChaincode) GetTokenState(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	tb, err := getToken(stub)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success([]byte(strconv.FormatBool(tb.Lock)))
}

func (t WalletTokenChaincode) GetDecimal(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	token, err := getToken(stub)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success([]byte(strconv.Itoa(token.Decimals)))
}

func (t WalletTokenChaincode) LockAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 2 {
		return shim.Error(parameterErr.Error())
	}
	from, to := args[0], args[1]
	if err := lockAccount(stub, from, to, true); err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

func (t WalletTokenChaincode) UnlockAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 2 {
		return shim.Error(parameterErr.Error())
	}
	from, to := args[0], args[1]
	if err := lockAccount(stub, from, to, false); err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(nil)
}

func lockAccount(stub shim.ChaincodeStubInterface, from, to string, res bool) error {
	token, err := getToken(stub)
	if err != nil {
		return err
	}
	if token.Owner != from {
		return permissionErr
	}
	token.LockAccount[to] = res
	return setToken(stub, token)
}

func (t WalletTokenChaincode) LockToken(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 1 {
		return shim.Error(parameterErr.Error())
	}
	key := args[0]
	if err := lockToken(stub, key, true); err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(nil)
}

func (t WalletTokenChaincode) UnLockToken(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 1 {
		return shim.Error(parameterErr.Error())
	}
	if err := lockToken(stub, args[0], false); err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(nil)
}

func lockToken(stub shim.ChaincodeStubInterface, from string, res bool) error {
	token, err := getToken(stub)
	if err != nil {
		return err
	}
	if token.Owner != from {
		return permissionErr
	}
	token.Lock = res
	return setToken(stub, token)
}

func (t WalletTokenChaincode) Mint(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 2 {
		return shim.Error(parameterErr.Error())
	}
	key := args[0]
	amount, bs := new(big.Int).SetString(args[1], 10)
	if !bs {
		return shim.Error(parameterErr.Error())
	}
	// 判断数值
	if !checkValue(*amount) {
		return shim.Error(valueErr.Error())
	}
	// 读取数据
	token, err := getToken(stub)
	if err != nil {
		return shim.Error(err.Error())
	}
	if token.Owner != key {
		return shim.Error(permissionErr.Error())
	}

	// 修改数据，判断回收额度由外部判断
	token.Supply = new(big.Int).Add(token.Supply, amount)
	// 写入数据
	err = setToken(stub, token)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(nil)
}

func (t WalletTokenChaincode) Burn(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 2 {
		return shim.Error(parameterErr.Error())
	}
	key := args[0]
	amount, bs := new(big.Int).SetString(args[1], 10)
	if !bs {
		return shim.Error(parameterErr.Error())
	}
	// 判断数值
	if !checkValue(*amount) {
		return shim.Error(valueErr.Error())
	}
	// 读取数据
	token, err := getToken(stub)
	if err != nil {
		return shim.Error(err.Error())
	}
	if token.Owner != key {
		return shim.Error(permissionErr.Error())
	}
	if token.Supply.Cmp(amount) < 0 {
		return shim.Error(valueErr.Error())
	}

	// 修改数据
	token.Supply = new(big.Int).Sub(token.Supply, amount)
	// 写入数据
	err = setToken(stub, token)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(nil)
}

func (t WalletTokenChaincode) SetOwner(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 2 {
		return shim.Error(parameterErr.Error())
	}
	from, to := args[0], args[1]
	token, err := getToken(stub)
	if err != nil {
		return shim.Error(err.Error())
	}
	if token.Owner != from {
		return shim.Error(permissionErr.Error())
	}
	token.Owner = to
	if err = setToken(stub, token); err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(nil)
}

func getLogger(stub shim.ChaincodeStubInterface) (c *shim.ChaincodeLogger) {
	fcn, _ := stub.GetFunctionAndParameters()
	c = shim.NewLogger(fmt.Sprintf("%s.%s.%s", stub.GetChannelID(), "walletToken", fcn))
	c.SetLevel(shim.LogDebug)
	return
}

// 调用其它链码
func invokeChaincode(stub shim.ChaincodeStubInterface, name string, trans [][]byte) ([]byte, error) {
	response := stub.InvokeChaincode(name, trans, "mychannel")
	if response.Status == 200 {
		return response.Payload, nil
	} else {
		return nil, errors.New(response.Message)
	}
}

var zero = new(big.Int).SetInt64(100000000)

var parameterErr = errors.New("parameter err")
var valueErr = errors.New("does not suppert this transaction")
var tokenLockErr = errors.New("token was locked")
var accountLockErr = errors.New("account was locked")
var balanceErr = errors.New("insufficient balance")
var approveBalanceErr = errors.New("allowance approve balance error")
var permissionErr = errors.New("permission denied")

/*
	封装读写数据
*/

func getToken(stub shim.ChaincodeStubInterface) (*Token, error) {
	tb, err := stub.GetState(TokenKey)
	if err != nil {
		return nil, err
	}
	if tb == nil {
		return nil, valueErr
	}
	var token Token
	err = json.Unmarshal(tb, &token)
	return &token, err
}

func setToken(stub shim.ChaincodeStubInterface, token *Token) error {
	tb, err := json.Marshal(token)
	if err != nil {
		return err
	}
	return stub.PutState(TokenKey, tb)
}

func has0xSuf(addr string) string {
	if strings.Index(addr, "0x") != 0 {
		return addr
	}
	return addr[2:]
}

func checkValue(value big.Int) bool {
	if value.Cmp(new(big.Int)) <= 0 {
		return false
	}
	valStr := value.String()
	if len(valStr) >= 21 {
		return false
	}
	return true
}

func queryTokenIsExist(stub shim.ChaincodeStubInterface, symbol string) ([]byte, error) {
	trans := [][]byte{[]byte("isExist"), []byte(symbol)}
	return invokeChaincode(stub, "tokenList", trans)
}

func getHisResult(result shim.HistoryQueryIteratorInterface) (bytes.Buffer, error) {
	//由于查询的结果是一个集合，所以要将结果集转成字符串，方便传输
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false

	for result.HasNext() {

		response, err := result.Next()
		if err != nil {
			return buffer, err
		}

		if bArrayMemberAlreadyWritten {
			buffer.WriteString(",")
		}

		buffer.WriteString("{\"TxId\":\"")
		buffer.WriteString(response.TxId)
		buffer.WriteString("\",\"value\":")

		//if response.IsDelete {
		//	buffer.WriteString("null")
		//} else {
		buffer.WriteString(string(response.Value))
		//}

		buffer.WriteString(", \"Timestamp\":\"")
		buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).Format("2006-01-02 15:04:05"))
		buffer.WriteString("\"")

		buffer.WriteString(", \"IsDelete\":\"")
		buffer.WriteString(strconv.FormatBool(response.IsDelete))
		buffer.WriteString("\"")

		buffer.WriteString("}")

		//item,_:= json.Marshal(response)
		//buffer.Write(item)

		bArrayMemberAlreadyWritten = true
	}

	buffer.WriteString("]")
	return buffer, nil
}